package com.fept.msgcore.msgserver.protocol.model.header;

import com.fept.msgcore.msgserver.protocol.exception.ProtocolErrorCodeEnum;
import com.fept.msgcore.msgserver.protocol.exception.ProtocolException;
import com.fept.msgcore.msgserver.protocol.model.base.BaseSegment;
import com.fept.msgcore.msgserver.util.system.ByteUtil;

/**
 * Created by Srma on 2/9/2017.
 */
public class Header  extends BaseSegment{

    /**
     *
     */
    private static final long serialVersionUID = -7598670529914644415L;
    /**
     * 协议版本号
     */
    private int version = 1;

    /**
     * 重试次数
     */
    private int retryCount = 0;

    /**
     * 帧ID
     */
    private int id;

    /**
     * 帧总长度
     */
    private int totalLength;

    /**
     * 发送机ID
     */
    private int senderId;
    /**
     * 消息中心ID
     */
    private int centerId;
    /**
     * 选项个数
     */
    private int optionNum;
    /**
     * 帧体个数
     */
    private int bodyNum;
    /**
     * 时间戳
     */
    private long timestamp;

    /**
     * header头部域集合
     */
    private static HeaderFields[] FIELDS = { HeaderFields.VERSION,
            HeaderFields.RETRY_COUNTER, HeaderFields.ID,
            HeaderFields.TOTAL_LENGTH, HeaderFields.SENDER_ID,
            HeaderFields.CENTER_ID, HeaderFields.OPTION_NUM,
            HeaderFields.BODY_NUMBER, HeaderFields.TIME_STAMP };

    private static final int FIELDS_LENGTH;

    static {
        int length = 0;
        for (HeaderFields hf : FIELDS) {
            length += hf.getFieldLength();
        }
        FIELDS_LENGTH = length;
    }

    /**
     *
     * @return
     */
    public static int getHeaderLength() {
        return FIELDS_LENGTH;
    }

    /**
     *
     */
    @Override
    public int getBytesLength() {
        return getHeaderLength();
    }

    @Override
    public void unpack(byte[] data, int offset) {
        if (data == null || data.length < getHeaderLength()) {
            throw new ProtocolException(ProtocolErrorCodeEnum.INVALID_DATA);
        }

        /**
         * version
         */
        this.setVersion(ByteUtil.getIntFromBytes(data, offset,
                HeaderFields.VERSION.getFieldLength()));
        offset += HeaderFields.VERSION.getFieldLength();

        /**
         * retryCount
         */
        this.setRetryCount(ByteUtil.getIntFromBytes(data, offset,
                HeaderFields.RETRY_COUNTER.getFieldLength()));
        offset += HeaderFields.RETRY_COUNTER.getFieldLength();

        /**
         * id
         */
        this.setId(ByteUtil.getIntFromBytes(data, offset,
                HeaderFields.ID.getFieldLength()));
        offset += HeaderFields.ID.getFieldLength();

        /**
         * totalLength
         */
        this.setTotalLength(ByteUtil.getIntFromBytes(data, offset,
                HeaderFields.TOTAL_LENGTH.getFieldLength()));
        offset += HeaderFields.TOTAL_LENGTH.getFieldLength();

        /**
         * senderId
         */
        this.setSenderId(ByteUtil.getIntFromBytes(data, offset,
                HeaderFields.SENDER_ID.getFieldLength()));
        offset += HeaderFields.SENDER_ID.getFieldLength();

        /**
         * centerId
         */
        this.setCenterId(ByteUtil.getIntFromBytes(data, offset,
                HeaderFields.CENTER_ID.getFieldLength()));
        offset += HeaderFields.CENTER_ID.getFieldLength();

        /**
         * option number
         */
        this.setOptionNum(ByteUtil.getIntFromBytes(data, offset,
                HeaderFields.OPTION_NUM.getFieldLength()));
        offset += HeaderFields.OPTION_NUM.getFieldLength();

        /**
         * body number
         */
        this.setBodyNum(ByteUtil.getIntFromBytes(data, offset,
                HeaderFields.BODY_NUMBER.getFieldLength()));
        offset += HeaderFields.BODY_NUMBER.getFieldLength();

        /**
         * timestamp
         */
        this.setTimestamp(ByteUtil.getLongFromBytes(data, offset,
                HeaderFields.TIME_STAMP.getFieldLength()));
        offset += HeaderFields.TIME_STAMP.getFieldLength();
    }

    @Override
    public byte[] pack() {
        byte[] headerBytes = new byte[getHeaderLength()];

        int offset = 0;

        /**
         * version
         */
        headerBytes[offset] = (byte) (this.version & 0xFF);
        offset += HeaderFields.VERSION.getFieldLength();

        /**
         * retryCount
         */
        headerBytes[offset] = (byte) (this.retryCount & 0xFF);
        offset += HeaderFields.RETRY_COUNTER.getFieldLength();

        /**
         * id
         */
        headerBytes[offset] = (byte) ((this.id >> 8) & 0xFF);
        headerBytes[offset + 1] = (byte) (this.id & 0xFF);
        offset += HeaderFields.ID.getFieldLength();

        /**
         * totalLength
         */
        headerBytes[offset] = (byte) ((this.totalLength >> 24) & 0xFF);
        headerBytes[offset + 1] = (byte) ((this.totalLength >> 16) & 0xFF);
        headerBytes[offset + 2] = (byte) ((this.totalLength >> 8) & 0xFF);
        headerBytes[offset + 3] = (byte) (this.totalLength & 0xFF);

        offset += HeaderFields.TOTAL_LENGTH.getFieldLength();

        /**
         * senderId
         */
        headerBytes[offset] = (byte) ((this.senderId >> 16) & 0xFF);
        headerBytes[offset + 1] = (byte) ((this.senderId >> 8) & 0xFF);
        headerBytes[offset + 2] = (byte) (this.senderId & 0xFF);

        offset += HeaderFields.SENDER_ID.getFieldLength();

        /**
         * centerId
         */
        headerBytes[offset] = (byte) ((this.centerId >> 16) & 0xFF);
        headerBytes[offset + 1] = (byte) ((this.centerId >> 8) & 0xFF);
        headerBytes[offset + 2] = (byte) (this.centerId & 0xFF);

        offset += HeaderFields.CENTER_ID.getFieldLength();

        /**
         * option number
         */
        headerBytes[offset] = (byte) (this.optionNum & 0xFF);
        offset += HeaderFields.OPTION_NUM.getFieldLength();

        /**
         * body number
         */
        headerBytes[offset] = (byte) (this.bodyNum & 0xFF);
        offset += HeaderFields.BODY_NUMBER.getFieldLength();

        /**
         * timestamp
         */
        /**
         * totalLength
         */
        headerBytes[offset] = (byte) ((this.timestamp >> 56) & 0xFF);
        headerBytes[offset + 1] = (byte) ((this.timestamp >> 48) & 0xFF);
        headerBytes[offset + 2] = (byte) ((this.timestamp >> 40) & 0xFF);
        headerBytes[offset + 3] = (byte) ((this.timestamp >> 32) & 0xFF);
        headerBytes[offset + 4] = (byte) ((this.timestamp >> 24) & 0xFF);
        headerBytes[offset + 5] = (byte) ((this.timestamp >> 16) & 0xFF);
        headerBytes[offset + 6] = (byte) ((this.timestamp >> 8) & 0xFF);
        headerBytes[offset + 7] = (byte) (this.timestamp & 0xFF);

        offset += HeaderFields.TIME_STAMP.getFieldLength();

        return headerBytes;
    }

    /**
     * @return the version
     */
    public int getVersion() {
        return version;
    }

    /**
     * @param version
     *            the version to set
     */
    public void setVersion(int version) {
        this.version = version;
    }

    /**
     * @return the totalLength
     */
    public int getTotalLength() {
        return totalLength;
    }

    /**
     * @param totalLength
     *            the totalLength to set
     */
    public void setTotalLength(int totalLength) {
        this.totalLength = totalLength;
    }

    /**
     * @return the id
     */
    public int getId() {
        return id;
    }

    /**
     * @param id
     *            the id to set
     */
    public void setId(int id) {
        this.id = id;
    }

    /**
     * @return the retryCount
     */
    public int getRetryCount() {
        return retryCount;
    }

    /**
     * @param retryCount
     *            the retryCount to set
     */
    public void setRetryCount(int retryCount) {
        this.retryCount = retryCount;
    }

    /**
     * @return the senderId
     */
    public int getSenderId() {
        return senderId;
    }

    /**
     * @param senderId
     *            the senderId to set
     */
    public void setSenderId(int senderId) {
        this.senderId = senderId;
    }

    /**
     * @return the centerId
     */
    public int getCenterId() {
        return centerId;
    }

    /**
     * @param centerId
     *            the centerId to set
     */
    public void setCenterId(int centerId) {
        this.centerId = centerId;
    }

    /**
     * @return the optionNum
     */
    public int getOptionNum() {
        return optionNum;
    }

    /**
     * @param optionNum
     *            the optionNum to set
     */
    public void setOptionNum(int optionNum) {
        this.optionNum = optionNum;
    }

    /**
     * @return the bodyNum
     */
    public int getBodyNum() {
        return bodyNum;
    }

    /**
     * @param bodyNum
     *            the bodyNum to set
     */
    public void setBodyNum(int bodyNum) {
        this.bodyNum = bodyNum;
    }

    /**
     * @return the timestamp
     */
    public long getTimestamp() {
        return timestamp;
    }

    /**
     * @param timestamp
     *            the timestamp to set
     */
    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }
}

enum HeaderFields {

    VERSION(1, "version"),

    RETRY_COUNTER(1, "retryCounter"),

    ID(2, "id"),

    TOTAL_LENGTH(4, "totalLength"),

    SENDER_ID(3, "senderId"),

    CENTER_ID(3, "centerId"),

    OPTION_NUM(1, "optionNumber"),

    BODY_NUMBER(1, "bodyNumber"),

    TIME_STAMP(8, "timestamp"),

    ;

    private int fieldLength;
    private String descrition;

    private HeaderFields(int fieldLength, String description) {
        this.fieldLength = fieldLength;
        this.descrition = description;
    }

    /**
     * @return the fieldLength
     */
    public int getFieldLength() {
        return fieldLength;
    }

    /**
     * @param fieldLength
     *            the fieldLength to set
     */
    public void setFieldLength(int fieldLength) {
        this.fieldLength = fieldLength;
    }

    /**
     * @return the descrition
     */
    public String getDescrition() {
        return descrition;
    }

    /**
     * @param descrition
     *            the descrition to set
     */
    public void setDescrition(String descrition) {
        this.descrition = descrition;
    }
}
